/*
 * Counter.cpp
 *
 *  Created on: 24 мая 2022 г.
 *      Author: nikitas
 */

#include "Counter.h"

namespace STC_calc{

const std::map<std::string, Counter_function> Counter::function_catalog
{
	{"set", &set},
	{"sum", &sum},
	{"+", &sum},
	{"diff", &diff},
	{"-", &diff},
	{"mult", &mult},
	{"*", &mult},
	{"div", &div},
	{"/", &div},
	{"pow", &pow},
	{"^", &pow},
	{"log", &log},
	{"ln", &ln},
	{"lg", &lg},
	{"sin", &sin},
	{"cos", &cos},
	{"tg", &tg},
	{"tan", &tg},
	{"ctg", &tg},
	{"ctan", &ctg}
};

bool Counter::can_convert(const std::string& str) noexcept
{
	return SemanticAnalyzer::can_convert(str);
}

template <typename T>
void Counter::check_args_size(const std::vector<T> args, const unsigned long int min)
{
	if (args.size() < min) throw(RuntimeErrorClass("Error: too few arguments", Enums::number_arguments));
}

long double Counter::count_second_arg(ldouble_vector args, Counter_function func)
{
	ldouble_vector var(args.size());

	try
	{
		var = args;
		std::reverse(var.begin(), var.end());
		var.pop_back();
		return func(var);
	}
	catch(RuntimeErrorClass& err)
	{
		throw(err);
	}
	catch (std::exception& e)
	{
		std::cout << e.what();
	}

	return 1;
}

void Counter::check_denominator(const long double den)
{
	if (den != 0) return;

	throw(RuntimeErrorClass("Error: division by 0", Enums::calculation));
}

long double Counter::set(ldouble_vector args)
{
	check_args_size(args, 1);
	if (args.size() > 1) throw (RuntimeErrorClass("Error: argument of set() should be only one"));
	return args[0];
}

long double Counter::sum(ldouble_vector args)
{
	return std::accumulate(args.begin(), args.end(), 0.0);
}
long double Counter::sum_str(string_vector args)
{
	return sum(SemanticAnalyzer::convert(args));
}

long double Counter::diff(ldouble_vector args)
{
	check_args_size(args, 1);

	long double (*func)(ldouble_vector)=&sum;
	const long double nominator = args.at(0);
	const long double denominator = count_second_arg(args, func);

	return nominator - denominator;
}
long double Counter::diff_str(string_vector args)
{
	return diff(SemanticAnalyzer::convert(args));
}

long double Counter::mult(ldouble_vector args)
{
	return std::accumulate(args.begin(), args.end(), 1, std::multiplies());
}
long double Counter::mult_str(string_vector args)
{
	return mult(SemanticAnalyzer::convert(args));
}

long double Counter::div(ldouble_vector args)
{
	check_args_size(args, 2);

	long double (*func)(ldouble_vector)=&sum;

	const long double nominator = args.at(0);
	const long double denominator = count_second_arg(args, func);

	check_denominator(denominator);

	return nominator/denominator;
}
long double Counter::div_str(string_vector args)
{
	return div(SemanticAnalyzer::convert(args));
}

long double Counter::pow(ldouble_vector args)
{
	check_args_size(args, 2);

	long double (*func)(ldouble_vector)=&sum;

	const long double nominator = args.at(0);

	const long double denominator = count_second_arg(args, func);
	return std::pow(nominator, denominator);



}
long double Counter::pow_str(string_vector args)
{
	return pow(SemanticAnalyzer::convert(args));
}

long double Counter::log(ldouble_vector args)
{
	check_args_size(args, 2);

	long double (*func)(ldouble_vector)=[](ldouble_vector args) -> long double
	{//like sum, but with logarithm
		long double ret = 0;
		for (auto a : args)
		{
			if (a <= 0) throw(RuntimeErrorClass("Error: logarithm argument is 0 or less", Enums::calculation));
			ret+=std::log(a);
		}
		return ret;
	};

	if (args.at(0) <= 0) throw(RuntimeErrorClass("Error: logarithm base is 0 or less", Enums::calculation));
	const long double base = std::log(args.at(0));
	const long double arg = count_second_arg(args, func);

	try
	{
		check_denominator(base);
	}
	catch(RuntimeErrorClass& err)
	{
		throw(RuntimeErrorClass("Error: base is 1", Enums::calculation));
	}

	if (std::isnan(arg)) throw (RuntimeErrorClass("Error: logarithm argument is 0 or less", Enums::calculation));

	return arg/base;
}
long double Counter::log_base(ldouble_vector args, long double base)
{
	ldouble_vector vec(args.size()+1);
	vec[0] = base;
	int counter = 1;
	for (auto num : args)
	{
		vec[counter] = num;
		counter++;
	}
	return log(vec);
}
long double Counter::log_str(string_vector args)
{
	return log(SemanticAnalyzer::convert(args));
}

long double Counter::ln(const ldouble_vector args)
{
	return log_base(args, std::exp(1));
}
long double Counter::ln_str(const string_vector args)
{
	return ln(SemanticAnalyzer::convert(args));
}

long double Counter::lg(const ldouble_vector args)
{
	return log_base(args, 10);
}
long double Counter::lg_str(const string_vector args)
{
	return lg(SemanticAnalyzer::convert(args));
}

long double Counter::sin(const ldouble_vector args)
{
	check_args_size(args, 1);

	long double angle = sum(args);

	if (Config::is_flag("Count in degrees")) angle*=360/(2*M_PI);

	return std::sin(angle);
}
long double Counter::sin_str(const string_vector args)
{
	return sin(SemanticAnalyzer::convert(args));
}

long double Counter::cos(const ldouble_vector args)
{
	check_args_size(args, 1);

	long double angle = sum(args);

	if (Config::is_flag("Count in degrees")) angle*=360/(2*M_PI);

	return std::cos(angle);
}
long double Counter::cos_str(const string_vector args)
{
	return cos(SemanticAnalyzer::convert(args));
}

long double Counter::tg(const ldouble_vector args)
{
	check_args_size(args, 1);

	long double angle = sum(args);

	if (Config::is_flag("Count in degrees")) angle*=360/(2*M_PI);

	long double ret = std::tan(angle);

	if (std::isnan(ret)) throw (RuntimeErrorClass("Error: tg is NaN", Enums::calculation));

	return ret;
}
long double Counter::tg_str(const string_vector args)
{
	return tg(SemanticAnalyzer::convert(args));
}

long double Counter::ctg(const ldouble_vector args)
{
	check_args_size(args, 1);

	long double angle = sum(args);

	if (Config::is_flag("Count in degrees")) angle*=360/(2*M_PI);

	long double ret = std::tan(angle);

	if (ret == 0) throw (RuntimeErrorClass("Error: ctg is NaN", Enums::calculation));

	return 1/ret;
}
long double Counter::ctg_str(const string_vector args)
{
	return ctg(SemanticAnalyzer::convert(args));
}

long double Counter::exec(Counter_function func, const ldouble_vector args)
{
	return func(args);
}
long double Counter::exec(const string_vector args)
{

	check_args_size(args, 2);

	Counter_function func = check_func(args.at(0));

	string_vector n_args(args.size());

	std::copy(args.begin()+1, args.end(), n_args.begin());
	n_args.pop_back();

	ldouble_vector result = SemanticAnalyzer::convert(n_args);

	const long double ret = func(result);

	return ret;
}

std::string Counter::trying_to_find_name(const std::string& name)
{
	std::string ret = name;

	if (function_catalog.find(name) != function_catalog.end())
		return ret;

	std::transform(ret.begin(), ret.end(), ret.begin(), tolower);

	if (function_catalog.find(name) != function_catalog.end())
		return ret;

	throw (RuntimeErrorClass("Error: invalid function name: " + name + " doesn't exist", Enums::fatal));
}

Counter_function Counter::check_func(const std::string& name)
{
	std::string new_name = trying_to_find_name(name);
	return function_catalog.at(new_name);
}

}/* namespace STC_calc */



